package com.wiscamp.ninechapters.problems.domain.services;

import com.wiscamp.ninechapters.common.GlobalDictionary;
import com.wiscamp.ninechapters.common.GlobalState;
import com.wiscamp.ninechapters.common.core.CodeHelper;
import com.wiscamp.ninechapters.common.core.DateTimeHelper;
import com.wiscamp.ninechapters.common.core.RandomHelper;
import com.wiscamp.ninechapters.common.exception.ServiceException;
import com.wiscamp.ninechapters.problems.domain.ProblemsErrorCodes;
import com.wiscamp.ninechapters.problems.domain.gateway.DictionaryGateway;
import com.wiscamp.ninechapters.problems.domain.gateway.TagGateway;
import com.wiscamp.ninechapters.problems.domain.gateway.UserGateway;
import com.wiscamp.ninechapters.problems.domain.models.problems.problems.Problem;
import com.wiscamp.ninechapters.problems.domain.models.tasks.Assignment;
import com.wiscamp.ninechapters.problems.domain.models.tasks.AssignmentDetail;
import com.wiscamp.ninechapters.problems.domain.models.tasks.Task;
import com.wiscamp.ninechapters.problems.domain.repositories.*;
import com.wiscamp.ninechapters.problems.enums.AssignmentTypes;
import com.wiscamp.ninechapters.problems.enums.TaskStatus;
import com.wiscamp.ninechapters.tags.domain.services.KnowledgePointService;
import cube.common.core.DateTimeUtils;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.common.query.PagedObjects;
import cube.ddd.common.query.PagedQuery;
import cube.ddd.domain.BaseDomainService;
import cube.ddd.exception.ExceptionUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class TaskService extends BaseDomainService {

    private final TaskRepository taskRepository;

    private final AssignmentRepository assignmentRepository;

    private final DictionaryGateway dictionaryGateway;

    private final ProblemSetRepository problemSetRepository;

    private final ProblemSetService problemSetService;

    private final ProblemService problemService;

    private final ProblemRepository problemRepository;

    private final TagGateway tagGateway;

    private final String CACHE_NAME_TASK = "tasks";

    public TaskService(TaskRepository taskRepository, AssignmentRepository assignmentRepository, DictionaryGateway dictionaryGateway, ProblemSetRepository problemSetRepository, ProblemSetService problemSetService, ProblemService problemService, ProblemRepository problemRepository, KnowledgePointService knowledgePointService, RegroupRuleRepository regroupRuleRepository, UserGateway userGateway, TagGateway tagGateway) {
        this.taskRepository = taskRepository;
        this.assignmentRepository = assignmentRepository;
        this.dictionaryGateway = dictionaryGateway;
        this.problemSetRepository = problemSetRepository;
        this.problemSetService = problemSetService;
        this.problemService = problemService;
        this.problemRepository = problemRepository;
        this.tagGateway = tagGateway;
    }

    @HandleServiceException
    public int getTasksQuantityByMonth() {
        //DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        //DateTimeFormatter datetimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDate now = LocalDate.now();
        LocalDate dateStart = DateTimeUtils.getFirstDayOfMonth(now.getYear(), now.getMonthValue());
        LocalDate dateEnd = DateTimeUtils.getLastDayOfMonth(now.getYear(), now.getMonthValue());
        //String datetimeStartText = dateStart.format(dateFormatter) + " 00:00:01";
        //String datetimeEndText = dateEnd.format(dateFormatter) + " 23:59:59";
        //LocalDateTime datetimeStart = LocalDateTime.parse(datetimeStartText, datetimeFormatter);
        //LocalDateTime datetimeEnd = LocalDateTime.parse(datetimeEndText, datetimeFormatter);
        return taskRepository.getTaskQuantityByDate(dateStart, dateEnd);
    }


    /**
     * Gets a practice task
     *
     * @param taskId
     * @return
     */
    @Cacheable(value = CACHE_NAME_TASK, key = "#taskId")
    @HandleServiceException
    public Task getTask(@Min(0) long taskId) {
        var task = taskRepository.getTask(taskId);
        if (Objects.isNull(task)) {
            ExceptionUtils.throwServiceException(ProblemsErrorCodes.TASK_NOT_FOUND, taskId);
        }
        return task;
    }

    /*@HandleServiceException
    public Task getTask(@Min(0) long assignmentId, @Min(0) long detailId, @Min(0) long setId, @Min(0) long userId) {
        return taskRepository.getTask(assignmentId, detailId, setId, userId);
    }*/

    @HandleServiceException
    public long getSetIdByTask(@Min(0) long taskId) {
        var task = getTask(taskId);
        if (Objects.isNull(task)) return 0;
        return task.getSetId();
    }

    /**
     * Gets practice tasks by pager
     *
     * @param q
     * @return
     */
    @HandleServiceException
    public PagedObjects<Task> getTasksByQuery(@NotNull PagedQuery query) {
        return taskRepository.getTasksByQuery(query);
    }

    /**
     * Gets a user's uncompleted practice tasks
     *
     * @param userId
     * @return
     */

    @HandleServiceException
    public Collection<Task> getUncompletedTasks(@Min(0) long userId) {
        int uncompletedStatus = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_NOTCOMPLETED,
                GlobalState.APPLICATION_ID);
        var tasks = taskRepository.getTasksByUserAndStatus(userId, uncompletedStatus);
        // remove daily challenge tasks
        return tasks.stream().filter(task -> !task.getSetName().contains("DC-")).collect(Collectors.toList());
    }

    /**
     * Gets a user's completed practice tasks by pager number
     *
     * @param userId
     * @param pageNumber
     * @return
     */
    @HandleServiceException
    public Collection<Task> getCompletedTaskByPage(@Min(0) long userId, @Min(0) int pageNumber) {
        int completedStatus = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_COMPLETED,
                GlobalState.APPLICATION_ID);
        return taskRepository.getPagedTasksByUserAndStatus(userId, pageNumber, completedStatus);
    }

    /*@HandleServiceException
    public Task getLastTaskBySet(@Min(0) long setId, @Min(0) long userId, @Min(0) int taskStatus) {
        return taskRepository.getLastTaskBySet(setId, userId, taskStatus);
    }*/

    private boolean removeUncompletedTask(@Min(0) long assignmentId, @Min(0) long setId, @NotNull Collection<Long> taskUserIds) {
        int uncompletedTaskStatusId = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_NOTCOMPLETED,
                GlobalState.APPLICATION_ID);
        var tasks = taskRepository.getTasksBySet(assignmentId, setId);
        // 所有指派用户列表
        Collection<Long> removeUserIds = tasks.stream().map(Task::getUserId)
                .collect(Collectors.toList());
        // 获取待移除用户列表
        removeUserIds.removeAll(taskUserIds);
        // 删除未完成任务
        taskRepository.removeTasksByStatus(assignmentId, setId, uncompletedTaskStatusId, removeUserIds);
        return true;
    }


    public Collection<Long> createTasksBySet(long setId, Collection<Long> userIds, long operatorId) {
        var assignment = new Assignment();
        assignment.setSetId(setId);
        assignment.setAssignmentUserId(operatorId);
        assignment.setAssignmentTypeId(AssignmentTypes.MENTEE.getValue());
        assignment.setAssignmentTime(LocalDateTime.now());
        for (var userId : userIds) {
            var detail = new AssignmentDetail();
            detail.setReferenceId(userId);
            assignment.getDetails().add(detail);
        }
        var newAssignment = assignmentRepository.saveAssignment(assignment);

        var taskIds = new ArrayList<Long>();
        for (var detail : newAssignment.getDetails()) {
            var task = new Task();
            task.setAssignmentId(newAssignment.getAssignmentId());
            task.setDetailId(detail.getDetailId());
            task.setUserId(detail.getReferenceId());
            task.setSetId(newAssignment.getSetId());
            task.setTaskStatusId(TaskStatus.NOT_COMPLETED.getValue());
            var newTask = createTask(task);
            taskIds.add(newTask.getTaskId());
        }
        return taskIds;
    }

    /**
     * Creates a practice task
     *
     * @param task
     * @return
     */
    @CachePut(value = CACHE_NAME_TASK, key = "#task.taskId")
    @HandleServiceException
    public Task createTask(@NotNull Task task) {
        return taskRepository.saveTask(task);
    }

    /**
     * Updates a practice task
     *
     * @param task
     * @return
     */
    @CachePut(value = CACHE_NAME_TASK, key = "#task.taskId")
    @HandleServiceException
    public Task updateTask(@NotNull Task task) {
        return taskRepository.saveTask(task);
    }



    /**
     * Gets items of a practice task
     *
     * @param taskId
     * @return
     */
    @HandleServiceException
    public Collection<Problem> getProblemsByTask(@Min(0) long taskId) {
        var task = getTask(taskId);
        if (task == null) return new ArrayList<>();
        // 获取练习任务关联的习题集
        var set = problemSetService.getProblemSet(task.getSetId());
        if (set == null) return new ArrayList<>();
        //List<Long> problemIds = set.getSetProblems().stream().map(SetProblem::getProblemId).collect(Collectors.toList());
        var problemIds = problemSetRepository.getProblemIdsBySet(set.getSetId());
        return problemService.getProblemsByIds(problemIds);
    }



    @HandleServiceException
    public void completeTask(@NotNull Task task) {
        if (task.getTaskStatusId() == TaskStatus.NOT_COMPLETED.getValue()) {
            task.setTaskStatusId(TaskStatus.COMPLETED.getValue());
            task.setCompleteTime(LocalDateTime.now());
            updateTask(task);
        } else {
            logger.error("Invalid status of the practice task: {}", task.getTaskId());
        }
    }

    /**
     * Abandons uncompleted practice tasks before seven days and deletes abandoned
     * practice tasks before thirty days
     */
    @HandleServiceException
    public void abandonUncompletedTasks() {
        // 14天以上未完成任务设置为abandon状态，30天以上abandon状态任务物理删除
        var today = LocalDate.now();
        var daysBefore = today.minusDays(14);
        var daysBefore2 = today.minusDays(30);
        var startDate = "2021-01-01";
        var endDate = DateTimeHelper.getDateString(daysBefore);
        var endDate2 = DateTimeHelper.getDateString(daysBefore2);
        int uncompletedStatus = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_NOTCOMPLETED,
                GlobalState.APPLICATION_ID);
        int abandonedStatus = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_ABANDONED,
                GlobalState.APPLICATION_ID);
        taskRepository.updateTasksStatus(uncompletedStatus, abandonedStatus, startDate, endDate);
        taskRepository.deleteAbandonTasks(abandonedStatus, startDate, endDate2);
    }

    // region daily challenge

    /**
     * Gets today's practice task of daily challenge for a user
     *
     * @param userId
     * @return
     */
    @HandleServiceException
    public long getTaskByDailyChallenge(@Min(0) long userId, int grade) {
        // 获取今日挑战习题集
        var set = problemSetService.getProblemSetOfDailyChallenge(grade);
        if (set == null) return 0;
        // 根据习题集为用户创建练习任务
        var userIds = new ArrayList<Long>();
        userIds.add(userId);
        Collection<Long> taskIds = createTasksBySet(set.getSetId(), userIds, 0);
        if (taskIds.size() > 0) {
            return (long) taskIds.toArray()[0];
        }
        return 0;
    }

    /**
     * whether a user has completed today's practice task of daily challenge
     * 判断用户是否已完成今日挑战任务
     *
     * @param userId
     * @return
     */
    @HandleServiceException
    public boolean isCompletedTaskOfDailyChallenge(@Min(0) long userId, int grade) {
        var set = problemSetService.getProblemSetOfDailyChallenge(grade);
        if (set == null) return false;
        long taskId = getCompletedTaskOfDailyChallenge(userId, set.getSetId());
        return taskId > 0;
    }

    /**
     * Gets id of completed practice task of daily challenge
     *
     * @param userId
     * @param setId  今日挑战习题集编号
     * @return
     */
    private long getCompletedTaskOfDailyChallenge(@Min(0) long userId, @Min(0) long setId) {
        int status = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_COMPLETED,
                GlobalState.APPLICATION_ID);
        Task task = taskRepository.getFirstTask(setId, userId, status);
        if (task != null) return task.getTaskId();
        return 0;
    }
    // endregion

    /*@HandleServiceException
    public Collection<Long> AssignSetToUsers(@Min(0) long setId, @NotBlank Collection<String> userCodes) {
        Collection<Long> taskIds = new ArrayList<>();
        for (var userCode : userCodes) {
            var userId = userGateway.getUserIdByCode(userCode, GlobalState.APPLICATION_ID);
            if (userId >= 0) {
                var userIds = new ArrayList<Long>();
                userIds.add(userId);
                var assignment = assignmentRepository.createAssignmentBySet(setId, userIds);
                taskIds.addAll(createTaskByAssignment(assignment));
            }
        }
        return taskIds;
    }*/

    /**
     * Creates a practice task by a assignment
     * 根据指派明细创建练习任务
     *
     * @param assignment
     */
    @HandleServiceException
    public Collection<Long> createTaskByAssignment(@NotNull Assignment assignment) {
        Collection<Long> taskIds = new ArrayList<>();
        // create distribution task
        int taskStatus = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_NOTCOMPLETED,
                GlobalState.APPLICATION_ID);
        var set = problemSetService.getProblemSet(assignment.getSetId());
        if (set == null) {
            var errorCode = "service-exception.exercise-set.id.notfound";
            throw new ServiceException(null, errorCode, "exercise set id: " + assignment.getSetId());
        }
        for (var detail : assignment.getDetails()) {
            long userId = detail.getReferenceId();
            long assignmentId = assignment.getAssignmentId();
            long detailId = detail.getDetailId();
            long setId = set.getSetId();
            // 仅在任务不存在时创建习题集任务
            var task = taskRepository.getTask(assignmentId, detailId, setId, userId);
            if (task == null) {
                task = new Task();
                task.setUserId(userId);
                task.setDetailId(detailId);
                task.setAssignmentId(assignmentId);
                task.setSetId(setId);
                task.setSetName(set.getSetName());
                task.setTaskStatusId(taskStatus);
                task = createTask(task);
            }
            taskIds.add(task.getTaskId());
        }
        return taskIds;
    }

    /**
     * Removes uncompleted practice tasks by a assignment
     *
     * @param assignment
     */
    @HandleServiceException
    public void removeUncompletedTaskByAssignment(@NotBlank Assignment assignment) {
        long distributionId = assignment.getAssignmentId();
        var set = problemSetService.getProblemSet(assignment.getSetId());
        if (set == null) {
            var errorCode = "service-exception.exercise-set.id.notfound";
            throw new ServiceException(null, errorCode, "exercise set id: " + assignment.getSetId());
        }
        Collection<Long> taskUserIds = assignment.getDetails().stream().map(AssignmentDetail::getReferenceId)
                .collect(Collectors.toList());
        removeUncompletedTask(distributionId, set.getSetId(), taskUserIds);
    }

    @HandleServiceException
    public int getTaskQuantity(long userId) {
        return taskRepository.getUserTasksQuantity(userId);
    }

    @HandleServiceException
    public int getUncompletedTaskQuantity(long userId) {
        int statusId = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.TASKSTATUS_NOTCOMPLETED,
                GlobalState.APPLICATION_ID);
        return taskRepository.getUserTaskQuantityByStatus(userId, statusId);
    }


    // region task for course outline

    /**
     * 根据课程大纲中明细关联知识点标签信息，为用户创建练习任务
     *
     * @param courseId
     * @param outlineId
     * @param userId
     * @return
     */
    @HandleServiceException
    public long createTaskByCourseOutline(@Min(0) long courseId, @Min(0) long outlineId, @Min(0) long userId) {
        var outline = tagGateway.getCourseOutline(courseId, outlineId);
        if (outline == null) return 0;

        var associatePoints = outline.getKnowledgePointsIds();
        var associateTags = outline.getTagIds();
        var videoUrls = outline.getVideoUrls();
        int quantity = outline.getProblemQuantity();
        if (quantity <= 0) quantity = 15;
        //outline.setKnowledgePoints(ParseHelper.parseLong(associatePoints, ","));
        //outline.setTagIds(ParseHelper.parseLong(associateTags, ","));
        //outline.setVideoUrls(ParseHelper.parseString(outline.getVideoUrl(), ","));

        // 去除本人当日已放入练习题集的题目，避免出重复题
        Collection<Long> assignedProblemIds = problemSetService.getAssignedProblemsByUser(userId);
        // 用于测试目的
        if (userId == 1 || userId == 10) assignedProblemIds = new ArrayList<>();

        // 根据标签和难度获取匹配题目
        var problemsInTags = problemRepository.getProblemIdsByTagAndDifficulty(outline.getTagIds(), GlobalState.DIFFICULTY_VALUE_MIN, GlobalState.DIFFICULTY_VALUE_MAX);
        // 移除本人本日已出题目
        problemsInTags.removeAll(assignedProblemIds);
        // 根据知识点和难度获取匹配题目
        var problemsInPoints = problemRepository.getProblemIdsByPointAndDifficulty(outline.getKnowledgePointsIds(), GlobalState.DIFFICULTY_VALUE_MIN, GlobalState.DIFFICULTY_VALUE_MAX);
        // 取标签和知识点包含题目交集
        problemsInTags.retainAll(problemsInPoints);
        // 获取指定数量题目
        Collection<Long> tempIds = RandomHelper.getRandomCollection(problemsInTags, quantity);
        if (tempIds.size() <= 0) {
            String errorCode = "service-exception.exercise-set.item.notenough";
            throw new ServiceException(null, errorCode, outlineId);
        }

        // 按难度对题目排序
        Collection<Long> problemIds = problemRepository.getSortedProblemIdsByDifficulty(tempIds);

        int exerciseType = dictionaryGateway.getDictionaryIdByCode(GlobalDictionary.SETTYPE_EXERCISE,
                GlobalState.APPLICATION_ID);
        var identity = CodeHelper.get62RadixDateTime();
        var setCode = "PS-" + identity;
        var setName = outline.getOutlineId() + "-" + outline.getOutlineName() + "-" + identity;
        var set = problemSetService.createProblemSetByProblems(problemIds, userId, setCode, setName, exerciseType, 60);
        if (set == null) return 0;
        var userIds = new ArrayList<Long>();
        userIds.add(userId);
        Collection<Long> taskIds = createTasksBySet(set.getSetId(), userIds, userId);
        if (taskIds.size() >= 1)
            return (long) taskIds.toArray()[0];
        return 0;
    }
    // endregion

    // region examination code
    /*@HandleServiceException
    public long applyExaminationCode(@Min(0) long userId, @NotBlank String examinationCode) {
        var code = taskRepository.getExaminationCode(examinationCode);
        if (code == null) {
            var errorCode = "service-exception.distribution-task.examinationCode.notfound";
            throw new ServiceException(null, errorCode, examinationCode);
        }
        Collection<Long> taskIds = createAssignmentTaskBySet(code.getSetId(), userId);
        if (taskIds.size() >= 1) {
            return (long) taskIds.toArray()[0];
        }
        return 0;
    }*/

    /*@HandleServiceException
    public long useExaminationCode(@Min(0) long userId, @NotBlank String examinationCode) {
        var code = taskRepository.getExaminationCode(examinationCode);
        if (code == null) {
            var errorCode = "service-exception.distribution-task.examinationCode.notfound";
            throw new ServiceException(null, errorCode, examinationCode);
        }
        Collection<Long> taskIds = createAssignmentTaskBySet(code.getSetId(), userId);
        if (taskIds.size() >= 1) {
            return (long) taskIds.toArray()[0];
        }
        return 0;
    }*/
    // enregion
}
